/*
  https://github.com/simplyhippy/pubsubclient
*/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <FS.h>

#define SYS_LED 2

struct Config {
	String DeviceName = "Arduino";
	String DeviceId = "1234567891234567";
	String Userid = "770702827";
	String Token = "197ace52cf9463c09d27983c87fa7961";

	//下面的无需修改
	String SSID;
	String Passwd;
	String Server = "192.168.137.1";
	String ProtocolType = "MQTT";

	//是否Debug
	bool IsDebug = true;
};
Config _config;
WiFiClient _espClient;
PubSubClient client(_espClient, _config.Server, 8885);

bool LoadConfig() {
	File configFile = SPIFFS.open("/config.json", "r");
	if (!configFile) {
		Serial.println("Failed to open config file");
		return false;
	}
	StaticJsonDocument<1024> doc;
	DeserializationError error = deserializeJson(doc, configFile);
	if (error) {
		Serial.println("Failed to read config file.");
		return false;
	}
	_config.SSID = doc["SSID"] | "fail";
	_config.Passwd = doc["Passwd"] | "fail";
	if (_config.SSID == "fail" || _config.Passwd == "fail") {
		return false;
	}
	else {
		Serial.println("Load wifi config from spiffs successful.");
		Serial.print("Loaded ssid: ");
		Serial.println(_config.SSID);
		Serial.print("Loaded passwd: ");
		Serial.println(_config.Passwd);
		return true;
	}
}

bool SaveConfig() {
	DynamicJsonDocument doc(1024);
	JsonObject root = doc.to<JsonObject>();
	root["SSID"] = _config.SSID;
	root["Passwd"] = _config.Passwd;
	root["Server"] = _config.Server;
	root["Token"] = _config.Token;

	File configFile = SPIFFS.open("/config.json", "w");
	if (!configFile) {
		Serial.println("Failed to open config file for writing");
		return false;
	}
	if (serializeJson(doc, configFile) == 0) {
		Serial.println("Failed to write to file");
		return false;
	}
	return true;
}

bool ServerWatchDog() {
	if (WiFi.status() != WL_CONNECTED) {
		BaseConfigWifiConnect();
	}
	if (client.connected()) {
		client.loop();
	}
	else
	{
		EasyLinkerMqttConnect();
	}
	return true;
}

void BaseConfigWifiConnect() {
	Serial.println("Use base config to connect wifi.");
	WiFi.mode(WIFI_STA);
	WiFi.begin(_config.SSID, _config.Passwd);
	//连接超时时间，30s后没有连接将会转入SmartConfig
	int timeout = 30000;
	while (WiFi.status() != WL_CONNECTED) {
		Serial.println("Wait to connect wifi...");
		digitalWrite(SYS_LED, LOW);
		delay(150);
		digitalWrite(SYS_LED, HIGH);
		delay(150);
		timeout = timeout - 300;
		if (timeout <= 0) {
			Serial.println("Wifi connect timeout, use smart config to connect...");
			SmartConfigWifiConnect();
			return;
		}
	}
	Serial.println("WiFi connected by base config.");
	Serial.print("SSID:");
	Serial.println(WiFi.SSID());
	Serial.print("IP Address:");
	Serial.println(WiFi.localIP());
	delay(100);
}

void SmartConfigWifiConnect()
{
	Serial.println("Use smart config to connect wifi.");
	WiFi.mode(WIFI_STA);
	WiFi.beginSmartConfig();
	while (1)
	{
		Serial.println("Wait to connect wifi...");
		digitalWrite(SYS_LED, LOW);
		delay(1000);
		digitalWrite(SYS_LED, HIGH);
		delay(1000);
		if (WiFi.smartConfigDone())
		{
			Serial.println("WiFi connected by smart config.");
			Serial.print("SSID:");
			Serial.println(WiFi.SSID());
			Serial.print("IP Address:");
			Serial.println(WiFi.localIP());
			_config.SSID = WiFi.SSID();
			_config.Passwd = WiFi.psk();
			if (!SaveConfig()) {
				Serial.println("Failed to save config");
			}
			else {
				Serial.println("Config saved");
			}
			break;
		}
	}
	delay(100);
}

void Callback(const MQTT::Publish& pub) {
	// handle message arrived
	String topic = pub.topic();
	String payload = pub.payload_string();
	int payloadLength = pub.payload_len();

	//This is debug output
	if (_config.IsDebug) {
		Serial.print("Length:");
		Serial.println(payloadLength);
		Serial.print("Topic:");
		Serial.println(topic);
		Serial.print("Payload:");
		Serial.println(payload);
	}

	DynamicJsonDocument doc(2048);
	DeserializationError error = deserializeJson(doc, payload);
	if (error) {
		Serial.println("Failed to read payload");
		return;
	}
	JsonObject obj = doc.as<JsonObject>();
	String type = obj["Type"].isNull() ? "" : obj["Type"].as<String>();
	String messageId = obj["MessageId"].isNull() ? "" : obj["MessageId"].as<String>();

	if (type == "Success" || type == "Error") {
		if (type == "Success") {
			ErrorMessage(messageId, 0, "Success");
		}
		else {
			ErrorMessage(messageId, obj["Data"]["Code"].as<int>(), obj["Data"]["Message"].as<String>());
		}
	}
	else if (type == "Control") {
		//Control(String messageId, String propertiy, String value)
		if (obj["Data"].isNull()) {
			Control(messageId, obj["Action"].as<String>(), "", "");
		}
		else {
			String  propertiy = "", value = "";
			if (!obj["Data"]["Propertiy"].isNull()) {
				propertiy = obj["Data"]["Propertiy"].as<String>();
			}
			if (!obj["Data"]["Value"].isNull()) {
				value = obj["Data"]["Value"].as<String>();
			}
			Control(messageId, obj["Action"].as<String>(), propertiy, value);
		}
	}
	else if (type == "Query") {
		Query(messageId, obj["Action"].as<String>());
	}
	else if (type == "Msg") {
		if (obj["Data"].isNull() || obj["Data"]["Content"].isNull())
		{
			return;
		}
		Message(messageId, obj["Data"]["Content"].as<String>());
	}
}

void EasyLinkerMqttConnect() {
	Serial.println("Beigin to connect to easylinker server.");
	while (true)
	{
		if (WiFi.status() != WL_CONNECTED) {
			return;
		}
		Serial.println("Try to connect easylinker server...");
		digitalWrite(SYS_LED, LOW);
		delay(300);
		digitalWrite(SYS_LED, HIGH);
		delay(300);
		if (!client.connected()) {
			if (client.connect(MQTT::Connect(_config.DeviceId)
				.set_auth(_config.Userid, _config.Token))) {
				Serial.println("Connected to easylinker server.");
				client.set_callback(Callback);
				Subscribe();
				break;
			}
			else {
				Serial.println("Could not connect to easylinker server.");
			}
		}
		delay(3000);
	}
}

void ConnectServer() {
	// Connect wifi
	if (LoadConfig()) {
		BaseConfigWifiConnect();
	}
	else {
		SmartConfigWifiConnect();
	}
	// Connect easylinker server
	EasyLinkerMqttConnect();
	digitalWrite(SYS_LED, HIGH);
}

void Subscribe() {
	if (client.connected())
	{
		// 按照需要订阅以下四个主题中的其中一个或多个，不可订阅其它主题。
		// 前两个是必要的。
		// /server/control/{deviceId}/{userKey}/#
		// /server/query/{deviceId}/{userKey}/#
		// /server/msg/{deviceId}/{userKey}/#
		// /server/heart
		client.subscribe(MQTT::Subscribe()
			.add_topic("/server/control/" + _config.DeviceId + "/" + _config.Token + "/#")
			.add_topic("/server/query/" + _config.DeviceId + "/" + _config.Token + "/#")
			.add_topic("/server/msg/" + _config.DeviceId + "/" + _config.Token + "/#")
			.add_topic("/server/heart"));
	}
}

void Publish(String topic, String message) {
	if (client.connected())
	{
		if (topic.length() == 0 || message.length() == 0) {
			return;
		}
		if (topic != "/client/control" && topic != "/client/query" && topic != "/client/msg" && topic != "/client/heart") {
			return;
		}
		client.publish(topic, message);
	}
}

void ErrorMessage(String messageId, int code, String message) {
	Serial.print("Type:");
	if (code == 0) {
		Serial.println("Success");
	}
	else {
		Serial.println("Error");
	}
	Serial.print("MessageId:");
	Serial.println(messageId);
	Serial.print("Code:");
	Serial.println(code);
	Serial.print("Message:");
	Serial.println(message);
}

void Control(String messageId, String action, String propertiy, String value) {
	if (_config.IsDebug) {
		Serial.print("Type:");
		Serial.println("Control");
		Serial.print("MessageId:");
		Serial.println(messageId);
		Serial.print("Action:");
		Serial.println(action);
		Serial.print("Propertiy:");
		Serial.println(propertiy);
		Serial.print("Value:");
		Serial.println(value);
	}

	if (action == "TurnOn" || action == "TurnOff") {
		if (value == "On") {
			StaticJsonDocument<1024> doc;
			doc["Device"] = _config.DeviceName;
			doc["Uid"] = _config.Userid;
			doc["Key"] = _config.Token;
			doc["ProtocolType"] = _config.ProtocolType;
			doc["Type"] = "Control";
			doc["Action"] = action;
			doc["MessageId"] = messageId;
			doc["DeviceId"] = _config.DeviceId;
			doc["Time"] = 0;
			JsonObject data = doc.createNestedObject("Data");
			data["PowerState"] = value;

			String result = "";
			serializeJson(doc, result);
			if (_config.IsDebug) {
				Serial.print("Result Length:");
				Serial.println(result.length());
				Serial.print("Result:");
				serializeJsonPretty(doc, Serial);
				Serial.println();
			}
			if (result.length() > 0) {
				Publish("/client/control", result);
			}
		}
		else if (value == "Off") {
			StaticJsonDocument<1024> doc;
			doc["Device"] = _config.DeviceName;
			doc["Uid"] = _config.Userid;
			doc["Key"] = _config.Token;
			doc["ProtocolType"] = _config.ProtocolType;
			doc["Type"] = "Control";
			doc["Action"] = action;
			doc["MessageId"] = messageId;
			doc["DeviceId"] = _config.DeviceId;
			doc["Time"] = 0;
			JsonObject data = doc.createNestedObject("Data");
			data["PowerState"] = value;

			String result = "";
			serializeJson(doc, result);
			if (_config.IsDebug) {
				Serial.print("Result Length:");
				Serial.println(result.length());
				Serial.print("Result:");
				serializeJsonPretty(doc, Serial);
				Serial.println();
			}
			if (result.length() > 0) {
				Publish("/client/control", result);
			}
		}
	}
}

void Query(String messageId, String action) {
	if (_config.IsDebug) {
		Serial.print("Type:");
		Serial.println("Query");
		Serial.print("MessageId:");
		Serial.println(messageId);
		Serial.print("Action:");
		Serial.println(action);
	}
}

void Message(String messageId, String msg) {
	if (_config.IsDebug) {
		Serial.print("Type:");
		Serial.println("Query");
		Serial.print("MessageId:");
		Serial.println(messageId);
		Serial.print("Message:");
		Serial.println(msg);
	}
}

void setup() {
	Serial.begin(115200);
	//Set SYS_LED
	pinMode(SYS_LED, OUTPUT);
	//Mount FS
	Serial.println("Mounting FS...");
	if (!SPIFFS.begin()) {
		Serial.println("Failed to mount file system");
		return;
	}
	WiFi.hostname("#" + _config.DeviceName + "-EasyLinker");
	//Connect WIFI
	ConnectServer();
}

void loop() {
	//watch dog,if wifi or server disconnected, it will auto reconnect.
	ServerWatchDog();
}
